package com.cmc6.user.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.DesensitizedUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.CryptoException;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers;
import com.cmc6.common.config.BaseConfig;
import com.cmc6.common.constant.BaseConstant;
import com.cmc6.common.constant.BaseRegexConstant;
import com.cmc6.common.exception.BaseBizCodeEnum;
import com.cmc6.common.mapper.UserIdMapper;
import com.cmc6.common.model.dto.NotEmptyIdSet;
import com.cmc6.common.model.dto.NotNullId;
import com.cmc6.common.model.entity.*;
import com.cmc6.common.util.*;
import com.cmc6.dept.model.entity.DeptRefUserDO;
import com.cmc6.dept.service.DeptRefUserService;
import com.cmc6.job.model.entity.JobRefUserDO;
import com.cmc6.job.service.JobRefUserService;
import com.cmc6.minio.model.dto.FileRemoveDTO;
import com.cmc6.minio.model.entity.FileDO;
import com.cmc6.minio.service.FileDbService;
import com.cmc6.minio.service.FileService;
import com.cmc6.role.service.RoleRefUserService;
import com.cmc6.user.exception.BizCodeEnum;
import com.cmc6.user.mapper.UserInfoMapper;
import com.cmc6.user.mapper.UserLoginMapper;
import com.cmc6.user.mapper.UserMapper;
import com.cmc6.user.mapper.UserSecurityMapper;
import com.cmc6.user.model.dto.UserAnalysisUserNumberDTO;
import com.cmc6.user.model.dto.UserInsertOrUpdateDTO;
import com.cmc6.user.model.dto.UserPageDTO;
import com.cmc6.user.model.dto.UserUpdatePasswordDTO;
import com.cmc6.user.model.entity.UserInfoDO;
import com.cmc6.user.model.entity.UserLoginDO;
import com.cmc6.user.model.entity.UserLoginWxDO;
import com.cmc6.user.model.entity.UserSecurityDO;
import com.cmc6.user.model.vo.UserAnalysisUserNumberVO;
import com.cmc6.user.model.vo.UserPageVO;
import com.cmc6.user.service.RegByWxService;
import com.cmc6.user.service.UserSecurityService;
import com.cmc6.user.service.UserService;
import com.cmc6.user.util.PasswordConverterUtil;
import com.cmc6.websocket.service.SocketService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class UserServiceImpl implements UserService {

    @Resource
    UserIdMapper userIdMapper;
    @Resource
    UserInfoMapper userInfoMapper;
    @Resource
    UserSecurityMapper userSecurityMapper;
    @Resource
    UserSecurityService userSecurityService;
    @Resource
    UserLoginMapper userLoginMapper;
    @Resource
    RoleRefUserService roleRefUserService;
    @Resource
    UserMapper baseMapper;
    @Resource
    RedisTemplate<String, DateTime> redisTemplate;
    @Resource
    FileService fileService;
    @Resource
    FileDbService fileDbService;
    @Resource
    SocketService socketService;
    @Resource
    JobRefUserService jobRefUserService;
    @Resource
    DeptRefUserService deptRefUserService;
    @Resource
    RedissonClient redissonClient;
    @Resource
    RegByWxService userLoginWxService;

    /**
     * 新增/修改 用户
     */
    @Override
    @Transactional
    public String insertOrUpdate(UserInsertOrUpdateDTO dto) {

        boolean emailBlank = StrUtil.isBlank(dto.getEmail());
        boolean phoneBlank = StrUtil.isBlank(dto.getPhone());
        // 手机号和邮箱必填一个
        if (emailBlank && phoneBlank) {
            ApiResult.error(BizCodeEnum.EMAIL_OR_PHONE_ARE_REQUIRED);
        }

        RLock lock =
            redissonClient.getLock(BaseConstant.PRE_REDISSON + BaseConstant.PRE_LOCK_EMAIL_REG_CODE + dto.getEmail());
        lock.lock();

        try {

            if (!emailBlank) {
                if (checkEmailExist(dto.getEmail(), dto.getId())) { // 邮箱不能重复
                    ApiResult.error(BizCodeEnum.EMAIL_HAS_BEEN_REGISTERED);
                }
            }
            if (!phoneBlank) {
                if (checkPhoneExist(dto.getPhone(), dto.getId())) { // 手机号不能重复
                    ApiResult.error(BizCodeEnum.PHONE_HAS_BEEN_REGISTERED);
                }
            }

            if (dto.getId() == null) {

                boolean hasPassword =
                    StrUtil.isNotBlank(dto.getPassword()) && StrUtil.isNotBlank(dto.getOrigPassword());

                // 新增时，密码可以不填
                if (hasPassword) {
                    // 非对称加密，解密 ↓
                    String paramValue = ParamUtil.getValueById(BaseConstant.RSA_PRIVATE_KEY_ID); // 获取非对称加密，私钥
                    dto.setOrigPassword(rsaDecrypt(dto.getOrigPassword(), paramValue)); // 非对称加密：解密
                    dto.setPassword(rsaDecrypt(dto.getPassword(), paramValue)); // 非对称加密：解密
                    // 非对称加密，解密 ↑

                    checkOrigPassword(dto.getOrigPassword()); // 检查密码是否合法
                }

                insert(dto, hasPassword);
            } else {
                deleteByIdSetSub(Collections.singleton(dto.getId())); // 先删除 子表数据
                update(dto); // 再进行修改操作
            }

            return BaseBizCodeEnum.API_RESULT_OK.getMsg();

        } finally {
            lock.unlock();
        }

    }

    /**
     * 检查密码是否合法
     */
    @Override
    public void checkOrigPassword(String origPassword) {

        if (StrUtil.isBlank(origPassword)) {
            ApiResult.error(BizCodeEnum.PASSWORD_NOT_BLANK);
        }

        // 校验密码是否合法
        if (!ReUtil.isMatch(BaseRegexConstant.PASSWORD_REGEXP, origPassword)) {
            ApiResult.error(BizCodeEnum.PASSWORD_RESTRICTIONS); // 不合法直接抛出异常
        }

    }

    private void insert(UserInsertOrUpdateDTO dto, boolean hasPassword) {

        // 插入 用户主表
        UserIdDO userIdDO = new UserIdDO();
        userIdDO.setUuid(IdUtil.simpleUUID());
        userIdMapper.insert(userIdDO);

        // 插入 用户基本信息表
        UserInfoDO userInfoDO = new UserInfoDO();
        userInfoDO.setUserId(userIdDO.getId());
        userInfoDO.setNickname(
            StrUtil.isBlank(dto.getNickname()) ? UserRegHelperServiceImpl.getDefaultNickname() : dto.getNickname());
        userInfoDO.setPersonalStatement(MyEntityUtil.getNotNullStr(dto.getPersonalStatement()));
        userInfoDO.setAvatarUrl(MyEntityUtil.getNotNullStr(dto.getAvatarUrl()));
        userInfoMapper.insert(userInfoDO);

        // 插入 用户基本信息表
        UserLoginDO userLoginDO = new UserLoginDO();
        userLoginDO.setUserId(userIdDO.getId());
        userLoginDO.setEmail(dto.getEmail());
        if (hasPassword) {
            userLoginDO.setPassword(PasswordConverterUtil.converter(dto.getPassword(), true));
        } else {
            userLoginDO.setPassword("");
        }
        userLoginDO.setPhone(MyEntityUtil.getNotNullStr(dto.getPhone()));
        userLoginMapper.insert(userLoginDO);

        // 插入 用户安全以及状态相关表
        UserSecurityDO userSecurityDO = new UserSecurityDO();
        userSecurityDO.setUserId(userIdDO.getId());
        userSecurityDO.setJwtSecretSuf(IdUtil.simpleUUID());
        userSecurityDO.setEnableFlag(dto.isEnableFlag());
        userSecurityMapper.insert(userSecurityDO);

        // 新增数据到子表
        insertOrUpdateSub(userIdDO.getId(), dto);

    }

    /**
     * 新增/修改 用户：新增数据到子表
     */
    private void insertOrUpdateSub(Long userId, UserInsertOrUpdateDTO dto) {

        // 新增数据到：角色用户关联表
        if (CollUtil.isNotEmpty(dto.getRoleIdSet())) {
            List<RoleRefUserDO> insertList = new ArrayList<>();
            for (Long item : dto.getRoleIdSet()) {
                RoleRefUserDO roleRefUserDO = new RoleRefUserDO();
                roleRefUserDO.setRoleId(item);
                roleRefUserDO.setUserId(userId);
                insertList.add(roleRefUserDO);
            }
            roleRefUserService.saveBatch(insertList);
        }

        // 新增数据到：岗位用户关联表
        if (CollUtil.isNotEmpty(dto.getJobIdSet())) {
            List<JobRefUserDO> insertList = new ArrayList<>();
            for (Long item : dto.getJobIdSet()) {
                JobRefUserDO jobRefUserDO = new JobRefUserDO();
                jobRefUserDO.setJobId(item);
                jobRefUserDO.setUserId(userId);
                insertList.add(jobRefUserDO);
            }
            jobRefUserService.saveBatch(insertList);
        }

        // 新增数据到：部门用户关联表
        if (CollUtil.isNotEmpty(dto.getDeptIdSet())) {
            List<DeptRefUserDO> insertList = new ArrayList<>();
            for (Long item : dto.getDeptIdSet()) {
                DeptRefUserDO deptRefUserDO = new DeptRefUserDO();
                deptRefUserDO.setDeptId(item);
                deptRefUserDO.setUserId(userId);
                insertList.add(deptRefUserDO);
            }
            deptRefUserService.saveBatch(insertList);
        }

    }

    private void update(UserInsertOrUpdateDTO dto) {

        Long userId = UserUtil.getCurrentUserIdSafe();
        Date date = new Date();

        // 用户基本信息表
        ChainWrappers.lambdaUpdateChain(userInfoMapper).eq(UserInfoDO::getUserId, dto.getId())
            .set(UserInfoDO::getNickname, dto.getNickname())
            .set(UserInfoDO::getPersonalStatement, MyEntityUtil.getNotNullStr(dto.getPersonalStatement()))
            .set(UserInfoDO::getAvatarUrl, MyEntityUtil.getNotNullStr(dto.getAvatarUrl()))
            .set(UserInfoDO::getUpdateTime, date).set(UserInfoDO::getUpdateId, userId).update();

        // 用户基本信息表，备注：这里不会更新密码
        ChainWrappers.lambdaUpdateChain(userLoginMapper).eq(UserLoginDO::getUserId, dto.getId())
            .set(dto.getEmail() != null, UserLoginDO::getEmail, dto.getEmail())
            .set(dto.getPhone() != null, UserLoginDO::getPhone, dto.getPhone()).set(UserLoginDO::getUpdateTime, date)
            .set(UserLoginDO::getUpdateId, userId).update();

        // 用户安全以及状态相关表
        ChainWrappers.lambdaUpdateChain(userSecurityMapper).eq(BaseUserSecurityDO::getUserId, dto.getId())
            .set(BaseUserSecurityDO::getEnableFlag, dto.isEnableFlag()).set(BaseUserSecurityDO::getUpdateTime, date)
            .set(BaseUserSecurityDO::getUpdateId, userId).update();

        // 新增数据到子表
        insertOrUpdateSub(dto.getId(), dto);
    }

    /**
     * 分页排序查询：用户
     */
    @Override
    public Page<UserPageVO> myPage(UserPageDTO dto) {

        Page<UserPageVO> resultPage = dto.getPage(); // 本接口返回值

        List<UserPageVO> records = baseMapper.myPage(resultPage, dto);

        for (UserPageVO item : records) {
            item.setEmail(DesensitizedUtil.email(item.getEmail())); // 脱敏
            item.setPhone(DesensitizedUtil.mobilePhone(item.getPhone())); // 脱敏
        }

        // 增加 admin账号
        if (dto.isAddAdminFlag() && dto.getPageSize() == -1) {
            UserPageVO pageVO = new UserPageVO();
            pageVO.setId(BaseConstant.ADMIN_ID);
            pageVO.setNickname(BaseConfig.adminUsername);
            records.add(pageVO);
            resultPage.setTotal(resultPage.getTotal() + 1); // total + 1
        }

        resultPage.setRecords(records);

        return resultPage;
    }

    /**
     * 注销用户
     */
    @Override
    @Transactional
    public String deleteByIdSet(NotEmptyIdSet notEmptyIdSet) {

        // 删除文件
        List<FileDO> fileDOList =
            fileDbService.lambdaQuery().in(BaseEntityTwo::getCreateId, notEmptyIdSet.getIdSet()).select(FileDO::getUrl)
                .list();
        if (fileDOList.size() != 0) {
            FileRemoveDTO fileRemoveDTO = new FileRemoveDTO();
            // 文件路径（包含文件名） set
            Set<String> urlSet = fileDOList.stream().map(FileDO::getUrl).collect(Collectors.toSet());
            fileRemoveDTO.setUrlSet(urlSet);
            fileService.remove(fileRemoveDTO);
        }

        deleteByIdSetSub(notEmptyIdSet.getIdSet()); // 删除关联表数据

        // 注销用户，备注：因为加了 @TableLogic注解，所以不会删除数据
        ChainWrappers.lambdaUpdateChain(userSecurityMapper).in(BaseUserSecurityDO::getUserId, notEmptyIdSet.getIdSet())
            .remove();

        // socket发送消息：下线账号
        socketService.offlineByUserIdSetForDeleteUser(notEmptyIdSet.getIdSet());

        return BaseBizCodeEnum.API_RESULT_OK.getMsg();
    }

    /**
     * 删除子表数据
     */
    private void deleteByIdSetSub(Set<Long> idSet) {

        // 删除：角色用户关联表数据
        roleRefUserService.lambdaUpdate().in(RoleRefUserDO::getUserId, idSet).remove();

        // 删除：岗位用户关联表数据
        jobRefUserService.lambdaUpdate().in(JobRefUserDO::getUserId, idSet).remove();

        // 删除：部门用户关联表数据
        deptRefUserService.lambdaUpdate().in(DeptRefUserDO::getUserId, idSet).remove();

        // 删除：微信 openid用户管理表：防止 一个openid，查询出多个 userId
        userLoginWxService.lambdaUpdate().in(UserLoginWxDO::getUserId, idSet).remove();

    }

    /**
     * 通过主键id，查看详情
     */
    @Override
    public UserPageVO infoById(NotNullId notNullId) {

        UserPageDTO userPageVO = new UserPageDTO();
        userPageVO.setId(notNullId.getId());
        List<UserPageVO> voList = baseMapper.myPage(null, userPageVO);

        if (voList.size() == 0) {
            return null;
        }

        // 获取：用户绑定的角色 idSet
        List<RoleRefUserDO> refUserDOList =
            roleRefUserService.lambdaQuery().eq(RoleRefUserDO::getUserId, notNullId.getId())
                .select(RoleRefUserDO::getRoleId).list();
        Set<Long> roleIdSet = refUserDOList.stream().map(RoleRefUserDO::getRoleId).collect(Collectors.toSet());

        // 获取：用户绑定的岗位 idSet
        List<JobRefUserDO> jobRefUserDOList =
            jobRefUserService.lambdaQuery().eq(JobRefUserDO::getUserId, notNullId.getId())
                .select(JobRefUserDO::getJobId).list();
        Set<Long> jobIdSet = jobRefUserDOList.stream().map(JobRefUserDO::getJobId).collect(Collectors.toSet());

        // 获取：用户绑定的部门 idSet
        List<DeptRefUserDO> deptRefUserDOList =
            deptRefUserService.lambdaQuery().eq(DeptRefUserDO::getUserId, notNullId.getId())
                .select(DeptRefUserDO::getDeptId).list();
        Set<Long> deptIdSet = deptRefUserDOList.stream().map(DeptRefUserDO::getDeptId).collect(Collectors.toSet());

        UserPageVO resVO = voList.get(0); // 本方法返回值
        resVO.setRoleIdSet(roleIdSet);
        resVO.setJobIdSet(jobIdSet);
        resVO.setDeptIdSet(deptIdSet);

        return resVO;
    }

    /**
     * 刷新用户 jwt私钥后缀
     */
    @Override
    @Transactional
    public String refreshJwtSecretSuf(NotEmptyIdSet notEmptyIdSet) {

        List<UserSecurityDO> dbList = ChainWrappers.lambdaQueryChain(userSecurityMapper)
            .in(BaseUserSecurityDO::getUserId, notEmptyIdSet.getIdSet()).list();

        if (dbList.size() == 0) {
            return BaseBizCodeEnum.API_RESULT_OK.getMsg();
        }

        for (UserSecurityDO item : dbList) {
            item.setUpdateId(null); // 触发更新
            item.setUpdateTime(null); // 触发更新
            item.setJwtSecretSuf(IdUtil.simpleUUID()); // 更新 jwt私钥后缀
        }

        userSecurityService.updateBatchById(dbList); // 批量操作

        return BaseBizCodeEnum.API_RESULT_OK.getMsg();
    }

    /**
     * 非对称加密：解密
     */
    @Override
    public String rsaDecrypt(String str) {
        String paramValue = ParamUtil.getValueById(BaseConstant.RSA_PRIVATE_KEY_ID); // 获取非对称加密，私钥
        return rsaDecrypt(str, paramValue); // 返回解密之后的 字符串
    }

    /**
     * 非对称加密：解密
     */
    @Override
    public String rsaDecrypt(String str, String privateKey) {
        if (StrUtil.isBlank(str)) {
            ApiResult.error(BaseBizCodeEnum.PARAMETER_CHECK_ERROR);
        }

        if (StrUtil.isBlank(privateKey)) {
            ApiResult.error(BizCodeEnum.PASSWORD_CHECK_ERROR);
        }

        RSA rsa = new RSA(privateKey, null);

        String decryptStr = null;
        try {
            decryptStr = rsa.decryptStr(str, KeyType.PrivateKey); // 非对称解密之后的字符串
        } catch (CryptoException e) {
            ApiResult.error(BizCodeEnum.ACCOUNT_NUMBER_AND_PASSWORD_NOT_VALID);
        }

        // 获取 时间戳
        String[] split = decryptStr.split(";");
        if (split.length != 2) {
            ApiResult.error(BaseBizCodeEnum.ILLEGAL_REQUEST);
        }

        // 校验 时间戳是否过期
        DateTime checkTime = DateUtil.date(Long.parseLong(split[1]));
        int compare = DateUtil.compare(checkTime, new Date());
        if (compare < 0) {
            ApiResult.error(BaseBizCodeEnum.ILLEGAL_REQUEST);
        }

        String redisKey = BaseConstant.PRE_REDIS_RSA_ENCRYPT + str;

        // 校验是否存在
        Boolean hasKey = redisTemplate.hasKey(redisKey);
        if (hasKey != null && hasKey) {
            ApiResult.error(BaseBizCodeEnum.ILLEGAL_REQUEST);
        }

        // 这个 key存入 redis，不能再次使用了
        redisTemplate.opsForValue().set(redisKey, checkTime, 1, TimeUnit.MINUTES);

        return split[0]; // 返回解密之后的 字符串
    }

    /**
     * 重置头像
     */
    @Override
    @Transactional
    public String resetAvatar(NotEmptyIdSet notEmptyIdSet) {

        ChainWrappers.lambdaUpdateChain(userInfoMapper).in(BaseUserInfoDO::getUserId, notEmptyIdSet.getIdSet())
            .set(UserInfoDO::getAvatarUrl, "").update();

        return BaseBizCodeEnum.API_RESULT_OK.getMsg();
    }

    /**
     * 修改密码
     */
    @Override
    @Transactional
    public String updatePassword(UserUpdatePasswordDTO dto) {

        Set<Long> userIdSet = dto.getIdSet();

        // 非对称加密，解密 ↓
        String paramValue = ParamUtil.getValueById(BaseConstant.RSA_PRIVATE_KEY_ID); // 获取非对称加密，私钥
        dto.setNewOrigPassword(rsaDecrypt(dto.getNewOrigPassword(), paramValue));
        dto.setNewPassword(rsaDecrypt(dto.getNewPassword(), paramValue));
        // 非对称加密，解密 ↑

        checkOrigPassword(dto.getNewOrigPassword()); // 检查密码是否合法

        // 更新数据库的密码
        String newPassword = PasswordConverterUtil.converter(dto.getNewPassword(), true);
        ChainWrappers.lambdaUpdateChain(userLoginMapper).in(UserLoginDO::getUserId, userIdSet)
            .set(UserLoginDO::getPassword, newPassword).update();

        NotEmptyIdSet notEmptyIdSet = new NotEmptyIdSet();
        notEmptyIdSet.setIdSet(userIdSet);
        refreshJwtSecretSuf(notEmptyIdSet); // 下线账号：刷新 jwt私钥后缀

        return BaseBizCodeEnum.API_RESULT_OK.getMsg();
    }

    /**
     * 检查邮箱是否存在
     */
    @Override
    public boolean checkEmailExist(String email, Long userId) {
        return baseMapper.checkEmailOrPhoneExist(email, userId, null) > 0;
    }

    /**
     * 检查手机号是否存在
     */
    @Override
    public boolean checkPhoneExist(String phone, Long userId) {
        return baseMapper.checkEmailOrPhoneExist(null, userId, phone) > 0;
    }

    /**
     * 统计用户数
     */
    @Override
    public List<UserAnalysisUserNumberVO> analysisUserNumber(UserAnalysisUserNumberDTO dto) {

        List<UserAnalysisUserNumberVO> resList = new ArrayList<>(); // 本方法返回值

        if (dto.getType() == 1) {
            // 生成【上个月全部 到现在】的数据
            resList = baseMapper.analysisUserNumber(MyDateUtil.lastMonthToNowByDay(), "%Y-%m-%d");
        } else if (dto.getType() == 2) {
            // 生成【上一年 到现在】的数据
            resList = baseMapper.analysisUserNumber(MyDateUtil.lastYearToNowByMonth(), "%Y-%m");
        } else if (dto.getType() == 3) {
            // 生成【所有（年）】的数据
            resList = baseMapper.analysisUserNumber(MyDateUtil.allYear(), "%Y");
        }

        return resList;
    }

}
